#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#include "../notifyADT/notifyADT.h"
#include "../include/TCPclient.h"
#include "../../common/include/commonTCP.h"
#include "../../common/include/filesystem.h"
#include "../../common/include/split.h"
#include "../../common/include/general.h"
#include "../include/client.h"
#include "../../common/include/common.h"


#define NAME_LEN    51
#define QUIT_POS    4
#define NAME_POS    0
#define SOCK_SIZE   sizeof(struct sockaddr_in)


/* Externs de client.c */
extern int logged;
extern watchD w;
extern char *syncPath;
extern char splitVal;
extern pthread_t threads[SPLITS_QUANTITY];
extern int sons[SPLITS_QUANTITY];
extern int doLS;
extern char lastComm[FILE_LEN];

struct sockaddr_in server = {AF_INET, 1234};
int sockfd = -1;
char sess_name[50] = {0};


int
mainTCP (void)
{

    (void)signal(SIGINT, sighandINTTCP);

    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    /* set up the transport end point */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        return fatal(   "ERROR: Socket call failed.\n"
                        "SOLUTION: Check client's mainTCP.\n");
    }

    logged = 0;

    readCommandsTCP();

    if (close (sockfd))
        return fatal(   "ERROR: Socket call failed.\n"
                        "SOLUTION: Check client's mainTCP.\n");

    return 1;
}


void
nombreTCP(void)
{
    char name[50] = {0};
    char msg[100] = {0};
    int nwrite, ans, totlen, splitRet, len = 0;

    fprintf (stdout, "\nInsert your name: ");
    getLine (name, MAX_COMMAND_LEN, stdin);

    strcpy(sess_name, name);

    len = strlen(name);

    sprintf(msg, "2,%d,%s", len, name);
    totlen = strlen (msg);

    /* connect the socket to the server's address */
    if(connect(sockfd, (struct sockaddr *)&server, SOCK_SIZE) == -1)
    {
        fatal(  "ERROR: Connect call failed.\n"
                "SOLUTION: Check client's nombreTCP.\n");
    }

    if ((nwrite = write(sockfd, msg, totlen)) == -1)
        fatal(  "ERROR: Message write failed.\n"
                "SOLUTION: Check client's nombreTCP.\n");

    ans = rcvTCPAns(sockfd);
    logged = 1;

    if (splitVal == 't')
        splitRet = split(listenSrvTCP, NULL, (void *)&threads[LISTSRV_SPLIT],
                    splitVal, endCiclesTCP);
    else
        splitRet = split(listenSrvTCP, NULL, (void *)&sons[LISTSRV_SPLIT],
                    splitVal, endCiclesTCP);


    if ( splitRet != 1)
        fatal(  "ERROR: Split failed.\n"
                "SOLUTION: Check SPLITS_QUANTITY.\n");

    return;
}

void
listaTCP(void)
{
    extern int logged;
    char msg[100] = {0};
    int nwrite;
    int len, totlen;
    char * trash = "trash";

    len =strlen(trash);

    sprintf(msg, "%d,%d,%s", COM_LIST, len, trash);
    totlen = strlen (msg);

    if ((nwrite = write(sockfd, msg, totlen)) == -1)
        fatal(  "ERROR: Message write failed.\n"
                "SOLUTION: Check client's listaTCP.\n");
    return;
}

void atenderListaTCP(void)
{
    int name_len, comm, index = 1;
    char dir[50] = {0};

    sndTCPAns(sockfd, COM_ACK);
    comm = readcomTCP(sockfd);

    while (comm != COM_END)
    {
        name_len = readsizeTCP(sockfd);

        if (readWithTimeout(sockfd, dir, name_len, TIMEOUT) < 0)
            fatal(  "ERROR: Msg read failed.\n"
                    "SOLUTION: Check server's status.\n");

        printf("%2d %s\n", index++, dir);

        while (name_len)
        {
            dir[--name_len] = 0;
        }

        sndTCPAns(sockfd, COM_ACK);
        comm = readcomTCP(sockfd);
    }

    return;
}

void agregarTCP(void)
{
    char msg[100] = {0};
    int nwrite, len, totlen;
    char dir[50] = {0};

    fprintf (stdout, "\nInsert a Dir: ");
    getLine (dir, MAX_COMMAND_LEN, stdin);

    len = strlen(dir);

    sprintf(msg, "4,%d,%s", len, dir);
    totlen = strlen (msg);


    if ((nwrite = send_data_TCP (sockfd, msg, totlen)) < 0)
        fatal(  "ERROR: Message write failed.\n"
                "SOLUTION: Check client's agregarTCP.\n");

    return ;
}

void atenderAddTCP (void)
{
    int size;
    char msgbuf[100] = {0};
    char currPath[FILE_LEN];
    char dir[FILE_LEN] = {0};
    int comm, nread, path_size;
    char path[100] = {0};
    char buffer[BUF_SIZE] = {0};
    FILE * pfile;
    char newpath[100] = {0};

    size = readsizeTCP(sockfd);
    size -= readParamTCP(dir, size);

    sprintf(currPath,"%s/%s",syncPath, dir);
    if (!startFolder(currPath))
    {
        fatal(  "ERROR: Couldn't start folder.\n"
                "SOLUTION: Check if there is enough disk space.\n");
    }

    sndTCPAns(sockfd, COM_ACK);
    comm = readcomTCP(sockfd);

    while (comm != COM_END)
    {
        path_size = readsizeTCP(sockfd);

        if (readWithTimeout(sockfd, msgbuf, path_size, TIMEOUT) < 0)
            fatal(  "ERROR: Msg read failed.\n"
                    "SOLUTION: Check server's status.\n");

        msgbuf[path_size] = 0;
        sscanf (msgbuf, "%d,%s", &size, path);

        /*it's a dir*/
        if (size == -1)
        {
            mkdir(path, 0744);
            sndTCPAns(sockfd, COM_ACK);
        }
        else
        {
            sprintf(newpath, "%s.new", path);
            if((pfile = fopen(newpath, "w")) == NULL)
                fatal(  "ERROR: Creating file.\n"
                        "SOLUTION: Check if there is enough disk space.\n");

            nread = 0;
            sndTCPAns(sockfd, COM_ACK);
            while (size > 0)
            {
                if (size >= BUF_SIZE)
                    nread = BUF_SIZE;
                else
                    nread = size;

                if (readWithTimeout(sockfd, buffer, nread, TIMEOUT) < 0)
                    {
                        sndTCPAns(sockfd, COM_ERR);
                        fatal(  "ERROR: Msg read failed.\n"
                                "SOLUTION: Check server's status.\n");
                    }

                    if ((fwrite(buffer, 1, nread, pfile)) < nread)
                    {
                        sndTCPAns(sockfd, COM_ERR);
                        printf ("File write failed\n");
                    }

                    sndTCPAns(sockfd, COM_ACK);

                    size -= nread;

            }
            if ((fclose (pfile)) == EOF)
                printf ("file close failed\n");

            if (rename(newpath, path) != 0)
            {
                printf ("Error renaming %s\n", newpath);
                exit(1);
            }
        }

        comm = readcomTCP(sockfd);
    }

    if(setWatchs(currPath, &w) == -1)
    {
        fatal(  "ERROR: Calling setWatchs.\n"
                "SOLUTION: Check client's atenderAddTCP.\n");
        return ;
    }
    printf("%s added successfully\n", dir);
    return;
}

void removerTCP (void)
{
    int len, nwrite, totlen;
    char msg [MSGSIZE];
    char dir[MAX_DIR_LEN];

    fprintf (stdout, "\nInsert a Sync Dir: ");
    getLine (dir, MAX_DIR_LEN, stdin);

    if (isDir(dir) != 1)
    {
        printf("%s is not a dir\n", dir);
        return ;
    }

    len = strlen (dir);

    sprintf(msg, "%d,%d,%s", COM_REMOVE, len, dir);
    totlen = strlen (msg);


    if ((nwrite = write(sockfd, msg, totlen)) == -1)
        fatal(  "ERROR: Message write failed.\n"
                "SOLUTION: Check client's removerTCP.\n");

    return;
}

void atenderRemoveTCP()
{
    int size;
    char dir[MAX_DIR_LEN] = {0};
    char w_dir[MAX_DIR_LEN] = {0};

    size = readsizeTCP(sockfd);

    size -= readParamTCP(dir, size);

    printf("dir to remove: %s\n", dir);

    sprintf(w_dir,"./%s", dir);

    if ((remWatch(&w, w_dir)) == -1)
        fatal(  "ERROR: Couldn't remove watch of dir.\n"
                "SOLUTION: Check client's atenderRemoveTCP.\n");

    return;
}

void salirTCP (void)
{
    int len, nwrite, totlen;
    char msg [MSGSIZE];

    len = strlen (sess_name);

    sprintf(msg, "%d,%d,%s", COM_CLOSE, len, sess_name);
    totlen = strlen (msg);

    if ((nwrite = write(sockfd, msg, totlen)) == -1)
        fatal("message write failed\n");

    if (close (sockfd) < 0)
        fatal ("fifo close failed\n");

    return;

}


void readCommandsTCP (void)
{
    int i;
    int cant_comms = 6;
    char command[MAX_COMMAND_LEN+1] = {0};

    char * commands[] = {"nombre", "lista", "agregar", "remover", "salir", "commit"};
    void (*pFunct[]) (void)= {nombreTCP, listaTCP, agregarTCP,
                                    removerTCP, salirTCP, commitTCP};

    fprintf (stdout, "\n<>: ");
    getLine (command, MAX_COMMAND_LEN, stdin);

    for (i=0 ; i < cant_comms+1 ; i++)
    {
        if (i == cant_comms)
        {
           fprintf (stdout, "\nError: Invalid command\n<>: ");
           getLine (command, MAX_COMMAND_LEN, stdin);
           i = -1;
        }
        else if (strcmp (command, commands[i]) == 0)
        {
            if(i == QUIT_POS)
            {
                closeAllTCP();
                exit(0);
                return;
            }

            if (i == NAME_POS)
            {
                pFunct[i] ();
            }
            else
            {
                if (!logged)
                {
                    fprintf (stdout, "Not logged in.\n");
                }
                else
                {
                    pFunct[i] ();
                }
            }
            fprintf (stdout, "\n<>: ");
            getLine (command, MAX_COMMAND_LEN, stdin);
            i = -1;
        }
    }

    return;
}

int commit_fileTCP (char * path, int type)
{
    int len, totlen;
    char msg[MSGSIZE] = {0};
    char aux_msg[MSGSIZE] = {0};
    int nwrite;
    char dir_path[FILE_LEN] = {0};
    int flag = 0, i;

    len = strlen (path);

    for (i = 0; i < len && flag < 2; i++)
    {
        dir_path[i] = path[i];

        if (path[i] == '/')
        {
            flag++;
        }
    }

    sprintf(aux_msg, "%s,%s,%d", sess_name, path, type);
    len = strlen(aux_msg);

    if (!canCommit (dir_path))
    {
        printf("Conflicts, can't commit\n");
        return 0;
    }

    sprintf(msg, "%d,%d,%s", COM_COMMIT, len, aux_msg);
    totlen = strlen (msg);

    if ((nwrite = send_data_TCP (sockfd, msg, totlen)) < 0)
        fatal("message write failed\n");

    return 1;
}

void atenderCommitTCP (void)
{
    int size;
    char path[FILE_LEN] = {0};
    int back;

    size = readsizeTCP(sockfd);
    size -= readParamTCP(path, size);

    if (snd_file_TCP(sockfd, path, 0) < 0)
    {
        printf ("%s not commited\n", path);
        return ;
    }

    if ((back = rcvTCPAns(sockfd)) != COM_ACK)
    {
        printf ("%s not commited, error received\n", path);
        return ;
    }
    return ;
}

void commitTCP (void)
{
    char aux_path[100] = {0};
    int resp;
    int type = 0;
    char chtype[MAX_COMMAND_LEN] = {0};
    char  * path = {0};

    fprintf (stdout, "\nInsert a Path: ");
    getLine (aux_path, MAX_COMMAND_LEN, stdin);

    path = strdup(aux_path);



    fprintf (stdout, "\nAdd (1) or Delete(0): ");
    getLine (chtype, MAX_COMMAND_LEN, stdin);

    sscanf(chtype, "%d", &type);

    resp = commit_fileTCP (path, type);

    if (resp == 1)
        printf("%s commited OK\n", path);
    else
        printf("%s commited ERROR\n", path);

    return;
}

void *
listenSrvTCP (void *parms)
{
    fd_set rfds;
    fd_set wrfds;
    char ch;
    struct timeval tw, tm;

    tm.tv_sec  = SEC_TIMEOUT;
    tm.tv_usec = 0;
    FD_ZERO(&rfds);

    /*Inicializo w*/
    w.waList = NULL;

    if ((w.fd = notify()) == -1)
    {
        fprintf(stderr, "Error when calling notify()\n");
        return (void *)NULL;
    }

    FD_SET(w.fd, &rfds);
    FD_SET(sockfd, &rfds);

    while(doLS)
    {
        memcpy(&wrfds, &rfds, sizeof(rfds));
        memcpy(&tw, &tm, sizeof(struct timeval));
        if(select(w.fd + 1, &wrfds, (fd_set *)0, (fd_set *)0, &tw) < 0)
        {
            if (errno == EINTR)
                continue;
            else
            {
                fatal ("select failed\n");
                return (void *)NULL;
            }
        }

        if (FD_ISSET (0, &wrfds))
        {
            read(0, &ch, 1);
            printf("%c\n", ch);
        }

        if (FD_ISSET (sockfd, &wrfds))
        {
            if (takeSrvTCP() < 0)
            {
                printf("ERROR: Check server status\n");
                exitSplit(splitVal);
            }
        }
        else if (FD_ISSET (w.fd, &wrfds))
        {
            makeCommitTCP();
            /* TODO: exit? */
//            exitSplit(splitVal);

        }
    }

    return (void *)NULL;
}

int takeSrvTCP (void)
{
    int com;

    if ((com = readcomTCP(sockfd)) < 0)
        return -1;

    switch (com)
    {
        case COM_SND_DIRLIST:
            atenderListaTCP();
            break;

        case COM_SND_FILES:
            printf("Add in progress!\n");
            atenderAddTCP();
            break;

        case COM_RCV_COMMIT:
            printf("Commit in progress!\n");
            atenderCommitTCP();
            break;

        case WILL_UPDATE:
            printf("Will update!\n");
            atenderWillUpdateTCP();
            break;

        /* TODO: cuando atiendo un update tengo ke blokear el sockfd para
         * las funcs de readCommands. Sino puedo interrumpir el update
         */
        case COM_SND_COMMIT:
            printf("Update in progress!\n");
            atenderUpdateTCP();
            break;

        case COM_RMV_OK:
            printf("Remove ok!\n");
            atenderRemoveTCP();
            break;

        case COMMIT_OK:
            printf("Commit (del) ok!\n");
            break;

        case DIR_ERR:
            printf("Invalid dir!\n");
            break;

        case IS_SYNC:
            printf("Dir already syncronized\n");
            break;

        case IS_BUSY:
            printf("Performing another action, please try later\n");
            break;

        case COM_ERR:
            printf("Error occurred\n");
            break;

        default :
            printf("Command not understood\n");
    }

    return 1;
}

int makeCommitTCP(void)
{
    int resp;
    int i = 0, len;
    char buf[BUF_LEN_NOTIF] = {0};
    char * path = NULL;
    struct inotify_event *event;
    char completePath[FILE_LEN] = {0};
    int flag;

    len = read(w.fd, buf, BUF_LEN_NOTIF);
    if(len == -1)
    {
        fprintf(stderr, "Error en read\n");
        return 0;
    }
    i = 0;

    while (i < len)
    {
        flag = 0;
        event = (struct inotify_event *) &buf[i];
        if (event->len == 0)
        {
            i += EVENT_SIZE + event->len;
            continue;
        }
        if ((path = getWatchCompletePath(event, &w)) == NULL)
        {
            i += EVENT_SIZE + event->len;
            continue;
        }
        sprintf(completePath, "%s/%s", path, event->name);
        free(path);

        if( (strcmp(fileExt(event->name), ".new") != 0) &&
            (strcmp(fileExt(event->name), ".old") != 0) &&
            (strcmp(fileExt(event->name), ".mine") != 0) )
        {
            if(strcmp(completePath, lastComm) != 0)
            {
                switch(event->mask)
                {
                case MOVE_TO_MASK:
                case ADD_DIR:
                case ADD_MASK:
                    resp = commit_fileTCP(completePath, 1);
                    if(resp)
                        takeSrvTCP();
                    break;

                case MOD_MASK:
                    resp = commit_fileTCP(completePath, 1);
                    if(resp)
                        takeSrvTCP();
                    break;

                case DEL_DIR:
                    resp = commit_fileTCP(completePath, 2);
                    if(resp)
                        takeSrvTCP();
                    break;

                case MOVE_FROM_MASK:
                case DEL_MASK:
                    resp = commit_fileTCP(completePath, 0);
                    if(resp)
                        takeSrvTCP();
                    break;

                default:
                    printf("Error en makeCommit con mask = %u\n", event->mask);
                    break;
                }
            }
            else
                clrtext(lastComm, FILE_LEN);
        }
        else
        {
            int k = strlen(completePath) - 1;
            sprintf(lastComm, "%s", completePath);
            while(lastComm[k] != '.' && k)
                k--;
            if (k)
                lastComm[k] = 0;
        }
        if(event->mask != DEL_DIR)
        {
            if(update(&w, event) == -1)
            {
                fprintf(stderr, "Error en upgrade\n");
            }
        }
        i += EVENT_SIZE + event->len;
    }
    return 1;
}

int readParamTCP (char * param, int size)
{
    int i = 0;

    do
    {
        readWithTimeout(sockfd, &(param[i]), 1, TIMEOUT);
        size--;
    }while ( size > 0 && param[i++] != ',');

    if(param[i-1] == ',')
        param[i-1] = 0;

    return i;
}

void atenderUpdateTCP (void)
{
    int size;
    char msgbuf[100] = {0};
    char currPath[FILE_LEN];
    char dir[50] = {0};
    int comm, nread;
    int path_size, type;;
    char path[100] = {0};
    char buffer[BUF_SIZE] = {0};
    FILE * pfile;
    char c;
    char newpath[100] = {0};


    size = readsizeTCP(sockfd);

    size -= readParamTCP(dir, size);

    if (readWithTimeout(sockfd, &c, 1, TIMEOUT) < 0)
        fatal(  "ERROR: Msg read failed.\n"
                "SOLUTION: Check server's status.\n");

    sscanf(&c, "%d", &type);
    sprintf(currPath,"%s",dir);

    sndTCPAns(sockfd, COM_ACK);

    if (type == ADD_MOD)
    {

        comm = readcomTCP(sockfd);

        path_size = readsizeTCP(sockfd);

        if (readWithTimeout(sockfd, msgbuf, path_size, TIMEOUT) < 0)
            fatal(  "ERROR: Msg read failed.\n"
                    "SOLUTION: Check server's status.\n");

        msgbuf[path_size] = 0;
        sscanf (msgbuf, "%d,%s", &size, path);
        sprintf(newpath, "%s.new", path);

        /*it's a dir*/
        if (size == -1)
        {
            if (mkdir(path, 0744) == -1)
            {
                printf("ERROR: creating dir: %s\n", path);
                return ;
            }
            sprintf(lastComm, "%s", path);

/*            if (rename(newpath, path) != 0)
            {
                printf ("Error renaming %s\n", newpath);
            }*/
            sndTCPAns(sockfd, COM_ACK);
        }
        else
        {

            if((pfile = fopen(newpath, "w")) == NULL)
                fatal ("Error creating file\n");

            nread = 0;
            sndTCPAns(sockfd, COM_ACK);
            while (size > 0)
            {
                if (size >= BUF_SIZE)
                    nread = BUF_SIZE;
                else
                    nread = size;

                if (readWithTimeout(sockfd, buffer, nread, TIMEOUT) < 0)
                {
                    sndTCPAns(sockfd, COM_ERR);
                    fatal(  "ERROR: Msg read failed.\n"
                            "SOLUTION: Check server's status.\n");
                }

                if ((fwrite(buffer, 1, nread, pfile)) < nread)
                {
                    sndTCPAns(sockfd, COM_ERR);
                    printf ("File write failed\n");
                }

                sndTCPAns(sockfd, COM_ACK);
                size -= nread;
            }

            if ((fclose (pfile)) == EOF)
                printf ("file close failed\n");

            if (rename(newpath, path) != 0)
            {
                printf ("Error renaming %s\n", newpath);
            }
        }
    }
    else if(type == DEL_COMMIT)
    {
/*        sprintf(newpath, "%s.new", dir);

        if (rename(dir, newpath) != 0)
        {
            printf ("Error renaming %s\n", newpath);
        }
*/
        if (remove(dir) != 0)
        {
                //sndFifoAns(fifo_serv, COM_ERR);
        }
        else
                //sndFifoAns(fifo_serv, COM_ACK);
            sprintf(lastComm, "%s", dir);
    }
    else if(type == DEL_DIR_COMMIT)
    {
/*        sprintf(newpath, "%s.new", dir);

        if (rename(dir, newpath) != 0)
        {
            printf ("Error renaming %s\n", newpath);
        }
*/
        if (rmdir(dir) != 0)
        {
                //sndFifoAns(fifo_serv, COM_ERR);
        }
        else
                //sndFifoAns(fifo_serv, COM_ACK);
            sprintf(lastComm, "%s", dir);
    }

    return;
}

void atenderWillUpdateTCP(void)
{
    int size;
    char  minePath[FILE_LEN] = {0};
    char dir[50] = {0};

    size = readsizeTCP(sockfd);

    if (readWithTimeout(sockfd, dir, size, TIMEOUT) < 0)
        fatal(  "ERROR: Msg read failed.\n"
        "SOLUTION: Check server's status.\n");

    sprintf(minePath, "%s.mine", dir);

    cp(dir, minePath);

    printf("Conflicts occured, fix before commiting\n");
    return ;
}

void
closeAllTCP(void)
{
    int i, stat;

    doLS = 0;

    if (logged)
    {
        salirTCP();

        if (splitVal == 't')
        {
            for( i = 0 ; i < SPLITS_QUANTITY ; i++)
            {
                pthread_join(threads[i], NULL);
            }
        }
        else
        {
            for( i = 0 ; i < SPLITS_QUANTITY ; i++)
            {
                kill(sons[i], SIGUSR1);
                waitpid (sons[i], &stat, 0);
            }
        }
    }

    exit(0);
    return;
}

void sighandINTTCP(int signo)
{
    printf("\nSIGINT catched\n");
    closeAllTCP();
}

void endCiclesTCP(int signo)
{
    doLS = 0;

    return;
}
